unit GLFileNGM;

interface

uses
    System.SysUtils,
    System.Classes,
    GLS.VectorLists,
    GLS.VectorGeometry,
    GLS.VectorFileObjects,
    GLS.Texture,
    GLS.ApplicationFileIO,
    GLS.Material,
    GLS.Utils;


type
    TGLNGMVectorFile = class(TGLVectorFile)
    public
         
        class function Capabilities: TGLDataFileCapabilities; override;
        procedure LoadFromStream(aStream: TStream); override;
        procedure SaveToStream(aStream: TStream); override;
    end;

    TLoadState = (sFindTag, sBones, sSkeleton, sTriangles, sQuads);

    TBipTag = (Bip01, Bip01Pelvis,
        Bip01LLeg, Bip01LLeg1,
        Bip01LFoot, Bip01LToe0,
        Bip01LToe01, Bip01LToe02,
        Bip01RLeg, Bip01RLeg1,
        Bip01RFoot, Bip01RToe0,
        Bip01RToe01, Bip01RToe02,
        Bip01Spine, Bip01Spine1,
        Bip01Spine2, Bip01Spine3,
        Bip01Neck, Bip01Head,
        Bip01LArm, Bip01LArm1,
        Bip01LArm2, Bip01LHand,
        Bip01LFinger0, Bip01LFinger01,
        Bip01LFinger02, Bip01LFinger1,
        Bip01LFinger11, Bip01LFinger12,
        Bip01RArm, Bip01RArm1,
        Bip01RArm2, Bip01RHand,
        Bip01RFinger0, Bip01RFinger01,
        Bip01RFinger02, Bip01RFinger1,
        Bip01RFinger11, Bip01RFinger12);
const
    MaxBipTag: Integer = 40;

    BipTagStr: array[0..39] of string = ('Bip01', 'Bip01 Pelvis',
        'Bip01 L Leg', 'Bip01 L Leg1',
        'Bip01 L Foot', 'Bip01 L Toe0',
        'Bip01 L Toe01', 'Bip01 L Toe02',
        'Bip01 R Leg', 'Bip01 R Leg1',
        'Bip01 R Foot', 'Bip01 R Toe0',
        'Bip01 R Toe01', 'Bip01 R Toe02',
        'Bip01 Spine', 'Bip01 Spine1',
        'Bip01 Spine2', 'Bip01 Spine3',
        'Bip01 Neck', 'Bip01 Head',
        'Bip01 L Arm', 'Bip01 L Arm1',
        'Bip01 L Arm2', 'Bip01 L Hand',
        'Bip01 L Finger0', 'Bip01 L Finger01',
        'Bip01 L Finger02', 'Bip01 L Finger1',
        'Bip01 L Finger11', 'Bip01 L Finger12',
        'Bip01 R Arm', 'Bip01 R Arm1',
        'Bip01 R Arm2', 'Bip01 R Hand',
        'Bip01 R Finger0', 'Bip01 R Finger01',
        'Bip01 R Finger02', 'Bip01 R Finger1',
        'Bip01 R Finger11', 'Bip01 R Finger12');

function BipTagToBoneName(Tag: TBipTag): string;
function BoneNameToBipTag(name: string): TBipTag;
procedure SmoothNormals(var mesh: TGLMeshObject);

procedure BuildNormals(var mesh: TGLMeshObject);

implementation

class function TGLNGMVectorFile.Capabilities: TGLDataFileCapabilities;
begin
    Result := [dfcRead, dfcWrite];
end;

procedure TGLNGMVectorFile.SaveToStream(aStream: TStream);
var
    sl: TStringList;
    i, j, k, l, m, wcount: Integer;
    p, r, v, n, t: TAffinevector;
    skel: TGLSkeletonMeshObject;
    added: boolean;
    b: string;
    procedure addbone(bone: TGLSkeletonBone; ParentID: Integer);
    var
        i: Integer;
    begin
        sl.add(format('%d "%s" %d', [bone.BoneID, bone.Name, ParentID]));
        for i := 0 to bone.Count - 1 do
            addbone(bone.Items[i], bone.BoneID);
    end;
begin
    sl := TStringList.Create;
    sl.Add('NGM 0.1');
    if owner is TGLActor then
    begin
        sl.add('reference');
        sl.add('bones');
        for i := 0 to Owner.Skeleton.RootBones.Count - 1 do
            addbone(Owner.Skeleton.RootBones[i], -1);
        sl.Add('end');
        if Owner.Skeleton.Frames.Count > 0 then
        begin
            sl.Add('skeleton');
            for i := 0 to Owner.Skeleton.Frames.Count - 1 do
            begin
                sl.Add('time 0');
                for j := 0 to Owner.Skeleton.Frames[0].Position.Count - 1 do
                begin
                    p := Owner.Skeleton.Frames[i].Position[j];
                    r := Owner.Skeleton.Frames[i].Rotation[j];
                    sl.add(format('%d %.6f %.6f %.6f %.6f %.6f %.6f', [j, p.X, p.Y, p.Z, r.X, r.Y, r.Z]));
                end;
            end;
            sl.Add('end');
        end;
    end
    else
        sl.add('static');

    added := false;
    if Owner.MeshObjects.Count > 0 then
    begin
        for i := 0 to Owner.MeshObjects.Count - 1 do
        begin
            if Owner.MeshObjects[i] is TGLSkeletonMeshObject then
                skel := TGLSkeletonMeshObject(Owner.MeshObjects[i])
            else
                skel := nil;
            with TGLMeshObject(Owner.MeshObjects[i]) do
                for j := 0 to FaceGroups.Count - 1 do
                begin
                    if TFGVertexNormalTexIndexList(FaceGroups[j]).Mode = fgmmQuads then
                        continue;
                    if not added then
                    begin
                        sl.Add('triangles');
                        added := true;
                    end;
                    sl.Add(FaceGroups[j].MaterialName);
                    with TFGVertexNormalTexIndexList(FaceGroups[j]) do
                        for k := 0 to (VertexIndices.Count div 3) - 1 do
                            for l := 0 to 2 do
                            begin
                                v := Vertices[VertexIndices[3 * k + l]];
                                n := Normals[NormalIndices[3 * k + l]];
                                t := TexCoords[TexCoordIndices[3 * k + l]];
                                if assigned(skel) then
                                begin
                                    b := '';
                                    wcount := 0;
                                    for m := 0 to skel.BonesPerVertex - 1 do
                                        with skel.VerticesBonesWeights[VertexIndices[3 * k + l]][m] do
                                            if weight > 0 then
                                            begin
                                                b := b + format(' %d %.4f', [BoneID, weight]);
                                                wcount := wcount + 1;
                                            end;
                                    sl.Add(Format('%.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f %d%s',
                                        [v.X, v.Y, v.Z, n.X, n.Y, n.Z, t.X, t.Y, wcount, b]));
                                end
                                else
                                    sl.Add(Format('%.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f',
                                        [v.X, v.Y, v.Z, n.X, n.Y, n.Z, t.X, t.Y]));
                            end;
                end;
        end;
        if added then
            sl.Add('end');
        added := false;
        for i := 0 to Owner.MeshObjects.Count - 1 do
        begin
            with TGLMeshObject(Owner.MeshObjects[i]) do
                for j := 0 to FaceGroups.Count - 1 do
                begin
                    if TFGVertexNormalTexIndexList(FaceGroups[j]).Mode = fgmmTriangles then
                        continue;

                    if not added then
                    begin
                        sl.Add('quads');
                        added := true;
                    end;
                    sl.Add(FaceGroups[j].MaterialName);
                    with TFGVertexNormalTexIndexList(FaceGroups[j]) do
                        for k := 0 to (VertexIndices.Count div 4) - 1 do
                            for l := 0 to 2 do
                            begin
                                v := Vertices[VertexIndices[4 * k + l]];
                                n := Normals[NormalIndices[4 * k + l]];
                                t := TexCoords[TexCoordIndices[4 * k + l]];
                                sl.Add(Format('%.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f',
                                    [v.X, v.Y, v.Z, n.X, n.Y, n.Z, t.X, t.Y]));
                            end;
                end;
        end;
        if added then
            sl.Add('end');
    end;
    sl.savetostream(astream);
    sl.free;
end;

procedure TGLNGMVectorFile.LoadFromStream(aStream: TStream);
var
    i, j, a, nVert, nTex, nnorm: Integer;
    nbBones, boneID: Integer;
    sl, ll: TStringList;
    bone: TGLSkeletonBone;
    frame: TGLSkeletonFrame;
    face: TFGVertexNormalTexIndexList;

    v: TAffineVector;
    s: string;
    astartframe, aendframe: Integer;
    f: Single;
    state: TLoadState;
    version: Single;
    currframe: Integer;
    skelmesh: TGLSkeletonMeshObject;
    mesh: TGLMeshObject;
    quat: TQuaternion;
    m: TGLMatrix;
    tran: TTransformations;
    dynArray: TVertexBoneWeightDynArray;
    MaxWeightCount, WeightCount, TriSectionStart: Integer;
    procedure AllocateMaterial(name: string);
    var
        matLib: TGLMaterialLibrary;
        fname: string;
    begin
        if Owner is TGLBaseMesh then
        begin
            matLib := TGLBaseMesh(GetOwner).MaterialLibrary;
            if Assigned(matLib) then
                if matLib.Materials.GetLibMaterialByName(name) = nil then
                    if CompareText(name, 'null') <> 0 then
                    try
                        fname := name;
                        if fileexists(name + '.bmp') then
                            fname := name + '.bmp';
                        if fileexists(name + '.jpg') then
                            fname := name + '.jpg';
                        if fileexists(name + '.tga') then
                            fname := name + '.tga';
                        if fileexists(name + '.png') then
                            fname := name + '.png';
                        matLib.AddTextureMaterial(name, fname);
                    except
                        on E: ETexture do
                        begin
                            if not Owner.IgnoreMissingTextures then
                                raise;
                        end;
                    end
                    else
                        matLib.AddTextureMaterial(name, '');
        end;
    end;

    procedure parseLine(line: string);
    var
        index: Integer;
    begin
        ll.clear;
        line := trim(line);
        index := pos(' ', line);
        while (index > 0) or ((pos('"', line) > 0) and (length(line) > 1)) do
        begin
            if line[1] = '"' then
            begin
                delete(line, 1, 1);
                if pos('"', line) > 0 then
                begin
                    index := pos('"', line);
                    delete(line, index, 1);
                end;
            end;

            ll.add(copy(line, 1, index - 1));

            delete(line, 1, index);
            line := trim(line);
            index := pos(' ', line);
        end;
        if length(line) > 0 then
            ll.add(line);
    end;
begin
    sl := TStringList.Create;
    ll := TStringList.Create;
    try
        sl.LoadFromStream(aStream);
        if Owner is TGLActor then
        begin
            if (sl.IndexOf('triangles') >= 0) then
            begin
                skelmesh := TGLSkeletonMeshObject.CreateOwned(Owner.MeshObjects);
                skelmesh.Mode := momFaceGroups;
            end
            else
                if Owner.MeshObjects.Count > 0 then
                    skelmesh := (Owner.MeshObjects[0] as TGLSkeletonMeshObject)
                else
                    raise Exception.Create('This is an animation file, load the reference first.');

            mesh := TGLMeshObject(skelmesh);
        end
        else
        begin
            //if sl.IndexOf('nodes') >= 0 then
            //    raise Exception.Create('Can''t load an Skeletal mesh into a static mesh object');
            if (sl.IndexOf('triangles') >= 0) or (sl.IndexOf('quads') >= 0) then
            begin
                mesh := TGLMeshObject.CreateOwned(Owner.MeshObjects);
                mesh.Mode := momFaceGroups;
            end
            else
                if Owner.MeshObjects.Count > 0 then
                    mesh := (Owner.MeshObjects[0] as TGLMeshObject)
                else
                    raise Exception.Create('Can''t load an animation file into a static mesh object');
            skelmesh := nil;
        end;

        if assigned(skelmesh) then
        begin
            nbBones := Owner.Skeleton.RootBones.BoneCount - 1;
            astartFrame := Owner.Skeleton.Frames.Count;
        end;

        state := sFindTag;
        currframe := -1;

        i := 0;
        while i < sl.count do
        begin
            parseline(sl[i]);
            case state of
                sFindTag:
                    if ll[0] = 'static' then
                    begin
                      //  if assigned(skelmesh) then
                      //      raise Exception.Create('Can''t load a static mesh into a Skeletal mesh object.');
                    end
                    else
                        if ll[0] = 'animation' then
                        begin
                            if assigned(skelmesh) then
                                if nbBones = 0 then
                                    raise Exception.Create('This is an animation file, load the reference model first.');
                        end
                        else
                            if ll[0] = 'NGM' then
                                version := strtofloatdef(ll[1])
                    //  if version>ver then
                    //     raise Exception.Create('File format newer than reader');
                            else
                                if (ll[0] = 'bones') and assigned(skelmesh) then
                                begin
                                    if Owner.Skeleton.RootBones.Count = 0 then
                                        state := sBones;
                                end
                                else
                                    if (ll[0] = 'skeleton') and assigned(skelmesh) then
                                    begin
                                        state := sSkeleton;
                                        frame := nil;
                                    end
                                    else
                                        if (ll[0] = 'triangles') then
                                        begin
                                            state := sTriangles;
                                            face := nil;
                                            if assigned(skelmesh) then
                                                if skelmesh.BonesPerVertex < 1 then
                                                    skelmesh.BonesPerVertex := 1;
                                        end
                                        else
                                            if (ll[0] = 'quads') or (ll[0] = 'portals') then
                                            begin
                                                state := sTriangles;
                                                face := nil;
                                                if assigned(skelmesh) then
                                                    if skelmesh.BonesPerVertex < 1 then
                                                        skelmesh.BonesPerVertex := 1;
                                            end
                   { else
                            if ll[0] = 'type' then
                            begin
                                 animationtype:= strtoint(ll[1]);
                            end
                            else
                            if ll[0] = 'framerate' then
                            begin
                                 framerate:= strtoint(ll[1]);
                           end
                            else
                            if ll[0] = 'startbone' then
                            end
                            else
                                startbone:= strtoint(ll[1]);
                            end;
                            if ll[0] = 'endbone' then
                            end
                            else
                                 endbone:= strtoint(ll[1]);  };
                sBones:
                    if (ll[0] = 'end') then
                    begin
                        state := sFindTag;
                        currframe := -1;
                    end
                    else
                    begin
                        bone := nil;
                        with Owner.Skeleton do
                            if (ll[2] <> '-1') then
                                bone := TGLSkeletonBone.CreateOwned(RootBones.BoneByID(StrToInt(ll[2])))
                            else
                                bone := TGLSkeletonBone.CreateOwned(RootBones);
                        if Assigned(bone) then
                        begin
                            bone.BoneID := StrToInt(ll[0]);
                            bone.Name := ll[1];
                        end;
                    end;
                sSkeleton:
                    if ll[0] = 'end' then
                    begin
                        if currframe > -1 then
                            with TGLActor(Owner).Animations.Add do
                            begin
                                a := Pos('.', ResourceName);
                                if a > 0 then
                                    Name := Copy(ResourceName, 1, a - 1)
                                else
                                    Name := ResourceName;

                                Reference := aarSkeleton;
                                StartFrame := astartFrame;
                                EndFrame := Self.Owner.Skeleton.Frames.Count - 1;
                            end;
                        state := sFindTag;
                    end
                    else
                        if ll[0] = 'time' then
                        begin
                            if strtoint(ll[1]) <> currframe then
                            begin
                                frame := TGLSkeletonFrame.CreateOwned(Owner.Skeleton.Frames);
                                currframe := strtoint(ll[1]);
                                a := Pos('.', ResourceName);
                                if a > 0 then
                                    frame.Name := Copy(ResourceName, 1, a - 1) + inttostr(currframe)
                                else
                                    frame.Name := ResourceName + inttostr(currframe);
                            end;
                        end
                        else
                            if currframe > -1 then
                            begin
                                frame.Position.Add(StrToFloatDef(ll[1]), StrToFloatDef(ll[2]), StrToFloatDef(ll[3]));
                                frame.TransformMode := sftRotation;
                                v.X := StrToFloatDef(ll[4]);
                                v.Y := StrToFloatDef(ll[5]);
                                v.Z := StrToFloatDef(ll[6]);

                                frame.Rotation.Add(v);
                       {  frame.TransformMode := sftQuaternion;
                        quat.RealPart := StrToFloatDef(ll[4]);
                        quat.ImagPart[0] := StrToFloatDef(ll[5]);
                        quat.ImagPart[1] := StrToFloatDef(ll[6]);
                        quat.ImagPart[2] := StrToFloatDef(ll[7]);

                        frame.Quaternion.Add(quat);  }
                            end;
                sTriangles:
                    if ll[0] = 'end' then
                    begin
                        state := sFindTag;
                        setlength(dynarray, 0);
                    end
                    else
                        if ((face = nil) or (face.MaterialName <> ll[0])) and (Pos(Copy(sl[i], 1, 1), '- 1234567890') = 0) then
                        begin
                            face := TFGVertexNormalTexIndexList.CreateOwned(mesh.FaceGroups);
                            face.Mode := fgmmTriangles;
                            face.MaterialName := ll[0];
                            AllocateMaterial(face.MaterialName);
                            MaxWeightCount := 0;
                            triSectionstart := i + 1;
                        end
                        else
                            if ll[0] <> face.MaterialName then
                            begin
                                if assigned(skelmesh) and (ll.count>8) then
                                begin

                                    if MaxWeightCount = 0 then
                                    begin
                                        j := TriSectionStart;
                                        while j < sl.count do
                                        begin
                                            parseline(sl[j]);
                                            if ll[0] = 'end' then
                                                break
                                            else
                                                if ll.count > 1 then
                                                begin
                                                    Weightcount := StrToInt(ll[8]);
                                                    if WeightCount > MaxWeightCount then
                                                        MaxWeightCount := WeightCount;
                                                end;
                                            j := j + 1;
                                        end;
                                        skelmesh.BonesPerVertex := MaxWeightCount;
                                        setlength(dynarray, MaxWeightcount);
                                        parseline(sl[TriSectionStart]);
                                    end;
                                    if ll.count > 1 then
                                    begin
                                        for j := 0 to MaxWeightCount - 1 do
                                            if ll.Count > 9 + (j * 2) then
                                            begin
                                                dynarray[j].BoneID := StrToInt(ll[9 + (j * 2)]);
                                                dynarray[j].Weight := StrToFloatDef(ll[10 + (j * 2)]);
                                            end
                                            else
                                            begin
                                                dynarray[j].BoneID := 0;
                                                dynarray[j].Weight := 0;
                                            end;

                                        skelmesh.FindOrAdd(dynArray,
                                            AffineVectorMake(StrToFloatDef(ll[0]), StrToFloatDef(ll[1]), StrToFloatDef(ll[2])),
                                            AffineVectorMake(StrToFloatDef(ll[3]), StrToFloatDef(ll[4]), StrToFloatDef(ll[5])));

                                    end;
                                end;
                                if ll.count > 1 then
                                    with mesh do
                                    begin
                                        nVert := vertices.FindorAdd(AffineVectorMake(StrToFloatDef(ll[0]), StrToFloatDef(ll[1]), StrToFloatDef(ll[2])));
                                //  nNorm := normals.Add(AffineVectorMake(StrToFloatDef(ll[3]), StrToFloatDef(ll[4]), StrToFloatDef(ll[5])));
                                        nTex := TexCoords.Add(AffineVectorMake(StrToFloatDef(ll[6]), StrToFloatDef(ll[7]), 0));
                                        colors.Add(1, 1, 1, 1);
                                        face.Add(nVert, nVert, nTex);
                                    end;
                            end;

                sQuads:
                    if ll[0] = 'end' then
                    begin
                        state := sFindTag;
                        setlength(dynarray, 0);
                    end
                    else
                        if ((face = nil) or (face.MaterialName <> ll[0])) and (Pos(Copy(sl[i], 1, 1), '- 1234567890') = 0) then
                        begin
                            face := TFGVertexNormalTexIndexList.CreateOwned(mesh.FaceGroups);
                            face.Mode := fgmmQuads;
                            face.MaterialName := ll[0];
                            AllocateMaterial(face.MaterialName);
                        end
                        else
                            if ll[0] <> face.MaterialName then
                            begin
                                if ll.count > 1 then
                                    with mesh do
                                    begin
                                        nVert := vertices.FindorAdd(AffineVectorMake(StrToFloatDef(ll[0]), StrToFloatDef(ll[1]), StrToFloatDef(ll[2])));
                                //  nNorm := normals.Add(AffineVectorMake(StrToFloatDef(ll[3]), StrToFloatDef(ll[4]), StrToFloatDef(ll[5])));
                                        nTex := TexCoords.Add(AffineVectorMake(StrToFloatDef(ll[6]), StrToFloatDef(ll[7]), 0));
                                        colors.Add(1, 1, 1, 1);
                                        face.Add(nVert, nVert, nTex);
                                    end;
                            end;
            end;
            inc(i);
        end;
        if assigned(skelmesh) then
        begin
            if Owner.Skeleton.RootBones.Count>0 then
            begin
            Owner.Skeleton.RootBones.PrepareGlobalMatrices;
            skelmesh.PrepareBoneMatrixInvertedMeshes;
            if owner is TGLActor then
                    TGLActor(owner).Reference:=aarSkeleton;
            end
            else
                if owner is TGLActor then
                    TGLActor(owner).Reference:=aarMorph;
           // owner.Skeleton.MakeSkeletalTranslationStatic(astartFrame, Owner.Skeleton.Frames.Count - 1);
        end;
        SmoothNormals(mesh);
    finally
        ll.Free;
        sl.Free;
    end;
end;

function BoneNameToBipTag(name: string): TBipTag;
var
    i: Integer;
begin
    for i := 0 to MaxBipTag - 1 do
        if BipTagStr[i] = name then
            result := TBipTag(i);
end;

function BipTagToBoneName(Tag: TBipTag): string;
begin
    result := BipTagStr[Integer(tag)];
end;

procedure SmoothNormals(var mesh: TGLMeshObject);
var
    norm, n: TAffinevector;
    face, face2: TFGVertexNormalTexIndexList;
    i, j, k, l, m: Integer;
    index: Integer;
begin
    mesh.Normals.Clear;
    for k := 0 to mesh.facegroups.Count - 1 do
    begin
        face := TFGVertexNormalTexIndexList(mesh.FaceGroups[k]);
        face.NormalIndices.Clear;
        for l := 0 to Face.VertexIndices.Count - 1 do
        begin
            norm := nullvector;
            index := Face.VertexIndices[l];
            for i := 0 to mesh.facegroups.Count - 1 do
            begin
                face2 := TFGVertexNormalTexIndexList(mesh.FaceGroups[i]);
                for j := 0 to Face2.VertexIndices.Count - 1 do
                    if face2.VertexIndices[j] = index then
                    begin
                        if face2.Mode = fgmmTriangles then
                            m := (j div 3) * 3
                        else
                            if face2.Mode = fgmmQuads then
                                m := (j div 4) * 4
                            else
                                continue;
                        n := calcplanenormal(mesh.vertices[face2.VertexIndices[m]], mesh.vertices[face2.VertexIndices[m + 1]], mesh.vertices[face2.VertexIndices[m + 2]]);
                        norm := vectoradd(norm, n);
                    end;
            end;
            face.NormalIndices.Add(mesh.Normals.add(vectornormalize(norm)));
        end;
    end;
end;

procedure BuildNormals(var mesh: TGLMeshObject);
var
    norm: TAffinevector;
    face: TFGVertexNormalTexIndexList;
    i, j, k: Integer;
    vcount: Integer;
begin
    mesh.Normals.Clear;
    for i := 0 to mesh.facegroups.Count - 1 do
    begin
        face :=TFGVertexNormalTexIndexList(mesh.FaceGroups[i]);
        face.NormalIndices.Clear;

        if face.Mode = fgmmTriangles then
            vcount := 3
        else
            if face.Mode = fgmmQuads then
                vcount := 4
            else
                continue;
        j := 0;
        while j < Face.VertexIndices.Count - 1 do
        begin
            norm := calcplanenormal(mesh.vertices[face.VertexIndices[j]], mesh.vertices[face.VertexIndices[j + 1]], mesh.vertices[face.VertexIndices[j+ 2]]);
            //mesh.Normals.findorAdd(norm);
            for k := 0 to vcount- 1 do
            begin
                mesh.Normals.Add(norm);
                face.NormalIndices.Add(mesh.Normals.Count-1);
            end;
            inc(j,vcount);
        end;
    end;
end;
initialization
    RegisterVectorFileFormat('ngm', 'NGL Model files', TGLNGMVectorFile);
end.

